// ***************************************************************
//  CompositeTag   version:  1.0   date: 12/18/2005
//  -------------------------------------------------------------
//  
//  -------------------------------------------------------------
//  Copyright ?2005 Winista - All Rights Reserved
// ***************************************************************
// 
// ***************************************************************
using System;

using Winista.Text.HtmlParser.Nodes;
using Winista.Text.HtmlParser.Scanners;
using Winista.Text.HtmlParser.Util;
using Winista.Text.HtmlParser.Visitors;

namespace Winista.Text.HtmlParser.Tags
{
	/// <summary> The base class for tags that have an end tag.
	/// Provided extra accessors for the children above and beyond what the basic
	/// <see cref="ITag"></see> provides. Also handles the conversion of it's children for
	/// the {@link #toHtml toHtml} method.
	/// </summary>
	[Serializable]
	public class CompositeTag:TagNode
	{
		/// <summary> The tag that causes this tag to finish.
		/// May be a virtual tag generated by the scanning logic.
		/// </summary>
		protected internal ITag mEndTag;
		
		/// <summary> The default scanner for non-composite tags.</summary>
		protected internal static readonly CompositeTagScanner mDefaultCompositeScanner = new CompositeTagScanner();
		
		public CompositeTag()
		{
			ThisScanner = mDefaultCompositeScanner;
		}

		/// <summary> Get the children as an array of <code>Node</code> objects.</summary>
		/// <returns> The children in an array.
		/// </returns>
		virtual public INode[] ChildrenAsNodeArray
		{
			get
			{
				return ((null == Children)?new INode[0]:Children.ToNodeArray());
			}
			
		}
		/// <summary> Return the HTML code for the children of this tag.</summary>
		/// <returns> A string with the HTML code for the contents of this tag.
		/// </returns>
		virtual public System.String ChildrenHTML
		{
			get
			{
				System.Text.StringBuilder buff = new System.Text.StringBuilder();
				for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
				{
					AbstractNode node = (AbstractNode) e.NextNode();
					buff.Append(node.ToHtml());
				}
				return buff.ToString();
			}
			
		}
		/// <summary> Return the number of child nodes in this tag.</summary>
		/// <returns> The child node count.
		/// </returns>
		virtual public int ChildCount
		{
			get
			{
				NodeList children;
				
				children = Children;
				
				return ((null == children)?0:children.Size());
			}
			
		}
		/// <summary> Return the text between the start tag and the end tag.</summary>
		/// <returns> The contents of the CompositeTag.
		/// </returns>
		virtual public System.String StringText
		{
			get
			{
				System.String ret;
				int start = EndPosition;
				int end = mEndTag.StartPosition;
				ret = Page.GetText(start, end);
				
				return (ret);
			}
		}

		/// <summary> Get an iterator over the children of this node.</summary>
		/// <returns> Am iterator over the children of this node.
		/// </returns>
		public virtual ISimpleNodeIterator GetChildren()
		{
			ISimpleNodeIterator ret;
			
			if (null != Children)
				ret = Children.Elements();
			else
				ret = (new NodeList()).Elements();
			
			return (ret);
		}
		
		/// <summary> Get the child of this node at the given position.</summary>
		/// <param name="index">The in the node list of the child.
		/// </param>
		/// <returns> The child at that index.
		/// </returns>
		public virtual INode GetChild(int index)
		{
			return ((null == Children)?null:Children.ElementAt(index));
		}
		
		/// <summary> Remove the child at the position given.</summary>
		/// <param name="i">The index of the child to remove.
		/// </param>
		public virtual void RemoveChild(int i)
		{
			if (null != Children)
				Children.Remove(i);
		}
		
		/// <summary> Return the child tags as an iterator.
		/// Equivalent to calling getChildren ().elements ().
		/// </summary>
		/// <returns> An iterator over the children.
		/// </returns>
		public virtual ISimpleNodeIterator Elements()
		{
			return ((null == Children)?new NodeList().Elements():Children.Elements());
		}
		
		/// <summary> Return the textual contents of this tag and it's children.</summary>
		/// <returns> The 'browser' text contents of this tag.
		/// </returns>
		public override System.String ToPlainTextString()
		{
			System.Text.StringBuilder stringRepresentation = new System.Text.StringBuilder();
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				stringRepresentation.Append(e.NextNode().ToPlainTextString());
			}
			return stringRepresentation.ToString();
		}
		
		/// <summary> Add the textual contents of the children of this node to the buffer.</summary>
		/// <param name="sb">The buffer to append to.
		/// </param>
		protected internal virtual void  PutChildrenInto(System.Text.StringBuilder sb)
		{
			INode node;
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				node = e.NextNode();
				// eliminate virtual tags
				//            if (!(node.getStartPosition () == node.getEndPosition ()))
				sb.Append(node.ToHtml());
			}
		}
		
		/// <summary> Add the textual contents of the end tag of this node to the buffer.</summary>
		/// <param name="sb">The buffer to append to.
		/// </param>
		protected internal virtual void PutEndTagInto(System.Text.StringBuilder sb)
		{
			// eliminate virtual tags
			//        if (!(endTag.getStartPosition () == endTag.getEndPosition ()))
			sb.Append(GetEndTag().ToHtml());
		}
		
		/// <summary> Return this tag as HTML code.</summary>
		/// <returns> This tag and it's contents (children) and the end tag
		/// as HTML code.
		/// </returns>
		public override System.String ToHtml()
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			sb.Append(base.ToHtml());
			if (!EmptyXmlTag)
			{
				PutChildrenInto(sb);
				if (null != GetEndTag())
					PutEndTagInto(sb);
			}
			return sb.ToString();
		}
		
		/// <summary> Searches all children who for a name attribute. Returns first match.</summary>
		/// <param name="name">Attribute to match in tag
		/// </param>
		/// <returns> Tag Tag matching the name attribute
		/// </returns>
		public virtual ITag SearchByName(System.String name)
		{
			INode node;
			ITag tag = null;
			bool found = false;
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes() && !found; )
			{
				node = e.NextNode();
				if (node is ITag)
				{
					tag = (ITag) node;
					System.String nameAttribute = tag.GetAttribute("NAME");
					if (nameAttribute != null && nameAttribute.Equals(name))
						found = true;
				}
			}
			if (found)
				return tag;
			else
				return null;
		}
		
		/// <summary> Searches for all nodes whose text representation contains the search string.
		/// Collects all nodes containing the search string into a NodeList.
		/// This search is <b>case-insensitive</b> and the search string and the
		/// node text are converted to uppercase using an English locale.
		/// For example, if you wish to find any textareas in a form tag containing
		/// "hello world", the code would be:
		/// <code>
		/// NodeList nodeList = formTag.searchFor("Hello World");
		/// </code>
		/// </summary>
		/// <param name="searchString">Search criterion.
		/// </param>
		/// <returns> A collection of nodes whose string contents or
		/// representation have the <code>searchString</code> in them.
		/// </returns>
		public virtual NodeList SearchFor(System.String searchString)
		{
			return (SearchFor(searchString, false));
		}
		
		/// <summary> Searches for all nodes whose text representation contains the search string.
		/// Collects all nodes containing the search string into a NodeList.
		/// For example, if you wish to find any textareas in a form tag containing
		/// "hello world", the code would be:
		/// <code>
		/// NodeList nodeList = formTag.searchFor("Hello World");
		/// </code>
		/// </summary>
		/// <param name="searchString">Search criterion.
		/// </param>
		/// <param name="caseSensitive">If <code>true</code> this search should be case
		/// sensitive. Otherwise, the search string and the node text are converted
		/// to uppercase using an English locale.
		/// </param>
		/// <returns> A collection of nodes whose string contents or
		/// representation have the <code>searchString</code> in them.
		/// </returns>
		public virtual NodeList SearchFor(System.String searchString, bool caseSensitive)
		{
			return (SearchFor(searchString, caseSensitive, new System.Globalization.CultureInfo("en")));
		}
		
		/// <summary> Searches for all nodes whose text representation contains the search string.
		/// Collects all nodes containing the search string into a NodeList.
		/// For example, if you wish to find any textareas in a form tag containing
		/// "hello world", the code would be:
		/// <code>
		/// NodeList nodeList = formTag.searchFor("Hello World");
		/// </code>
		/// </summary>
		/// <param name="searchString">Search criterion.
		/// </param>
		/// <param name="caseSensitive">If <code>true</code> this search should be case
		/// sensitive. Otherwise, the search string and the node text are converted
		/// to uppercase using the locale provided.
		/// </param>
		/// <param name="locale">The locale for uppercase conversion.
		/// </param>
		/// <returns> A collection of nodes whose string contents or
		/// representation have the <code>searchString</code> in them.
		/// </returns>
		public virtual NodeList SearchFor(System.String searchString, bool caseSensitive, System.Globalization.CultureInfo locale)
		{
			INode node;
			System.String text;
			NodeList ret;
			
			ret = new NodeList();
			
			if (!caseSensitive)
				searchString = searchString.ToUpper(locale);
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				node = e.NextNode();
				text = node.ToPlainTextString();
				if (!caseSensitive)
					text = text.ToUpper(locale);
				if (- 1 != text.IndexOf(searchString))
					ret.Add(node);
			}
			
			return (ret);
		}
		
		/// <summary> Collect all objects that are of a certain type
		/// Note that this will not check for parent types, and will not
		/// recurse through child tags
		/// </summary>
		/// <param name="classType">The class to search for.
		/// </param>
		/// <param name="recursive">If true, recursively search through the children.
		/// </param>
		/// <returns> A list of children found.
		/// </returns>
		public virtual NodeList SearchFor(System.Type classType, bool recursive)
		{
			return ((null == Children)?new NodeList():Children.SearchFor(classType, recursive));
		}
		
		/// <summary> Returns the node number of the first node containing the given text.
		/// This can be useful to index into the composite tag and get other children.
		/// Text is compared without case sensitivity and conversion to uppercase
		/// uses an English locale.
		/// </summary>
		/// <param name="text">The text to search for.
		/// </param>
		/// <returns> int The node index in the children list of the node containing
		/// the text or -1 if not found.
		/// </returns>
		/// <seealso cref="findPositionOf (String, Locale)">
		/// </seealso>
		public virtual int FindPositionOf(System.String text)
		{
			return (FindPositionOf(text, new System.Globalization.CultureInfo("en")));
		}
		
		/// <summary> Returns the node number of the first node containing the given text.
		/// This can be useful to index into the composite tag and get other children.
		/// Text is compared without case sensitivity and conversion to uppercase
		/// uses the supplied locale.
		/// </summary>
		/// <returns> int The node index in the children list of the node containing
		/// the text or -1 if not found.
		/// </returns>
		/// <param name="locale">The locale to use in converting to uppercase.
		/// </param>
		/// <param name="text">The text to search for.
		/// </param>
		public virtual int FindPositionOf(System.String text, System.Globalization.CultureInfo locale)
		{
			INode node;
			int loc;
			
			loc = 0;
			text = text.ToUpper(locale);
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				node = e.NextNode();
				if (- 1 != node.ToPlainTextString().ToUpper(locale).IndexOf(text))
					return loc;
				loc++;
			}
			return - 1;
		}
		
		/// <summary> Returns the node number of a child node given the node object.
		/// This would typically be used in conjuction with digUpStringNode,
		/// after which the string node's parent can be used to find the
		/// string node's position. Faster than calling findPositionOf(text)
		/// again. Note that the position is at a linear level alone - there
		/// is no recursion in this method.
		/// </summary>
		/// <param name="searchNode">The child node to find.
		/// </param>
		/// <returns> The offset of the child tag or -1 if it was not found.
		/// </returns>
		public virtual int FindPositionOf(INode searchNode)
		{
			INode node;
			int loc = 0;
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				node = e.NextNode();
				if (node == searchNode)
				{
					return loc;
				}
				loc++;
			}
			return - 1;
		}
		
		/// <summary> Get child at given index</summary>
		/// <param name="index">The index into the child node list.
		/// </param>
		/// <returns> Node The child node at the given index or null if none.
		/// </returns>
		public virtual INode ChildAt(int index)
		{
			return ((null == Children)?null:Children.ElementAt(index));
		}
		
		/// <summary> Collect this node and its child nodes (if-applicable) into the list parameter,
		/// provided the node satisfies the filtering criteria.
		/// <p>This mechanism allows powerful filtering code to be written very easily,
		/// without bothering about collection of embedded tags separately.
		/// e.g. when we try to get all the links on a page, it is not possible to
		/// get it at the top-level, as many tags (like form tags), can contain
		/// links embedded in them. We could get the links out by checking if the
		/// current node is a {@link CompositeTag}, and going through its children.
		/// So this method provides a convenient way to do this.</p>
		/// <p>Using collectInto(), programs get a lot shorter. Now, the code to
		/// extract all links from a page would look like:
		/// <pre>
		/// NodeList list = new NodeList();
		/// NodeFilter filter = new TagNameFilter ("A");
		/// for (NodeIterator e = parser.elements(); e.hasMoreNodes();)
		/// e.nextNode().collectInto(list, filter);
		/// </pre>
		/// Thus, <code>list</code> will hold all the link nodes, irrespective of how
		/// deep the links are embedded.</p>
		/// <p>Another way to accomplish the same objective is:
		/// <pre>
		/// NodeList list = new NodeList();
		/// NodeFilter filter = new TagClassFilter (LinkTag.class);
		/// for (NodeIterator e = parser.elements(); e.hasMoreNodes();)
		/// e.nextNode().collectInto(list, filter);
		/// </pre>
		/// This is slightly less specific because the LinkTag class may be
		/// registered for more than one node name, e.g. &lt;LINK&gt; tags too.</p>
		/// </summary>
		/// <param name="list">The list to add nodes to.
		/// </param>
		/// <param name="filter">The filter to apply.
		/// </param>
		public override void CollectInto(NodeList list, INodeFilter filter)
		{
			base.CollectInto(list, filter);
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
				e.NextNode().CollectInto(list, filter);
			if ((null != GetEndTag()) && (this != GetEndTag()))
				// 2nd guard handles <tag/>
				GetEndTag().CollectInto(list, filter);
		}
		
		/// <summary> Tag visiting code.
		/// Invokes <code>accept()</code> on the start tag and then
		/// walks the child list invoking <code>accept()</code> on each
		/// of the children, finishing up with an <code>accept()</code>
		/// call on the end tag. If <code>shouldRecurseSelf()</code>
		/// returns true it then asks the visitor to visit itself.
		/// </summary>
		/// <param name="visitor">The <code>NodeVisitor</code> object to be signalled
		/// for each child and possibly this tag.
		/// </param>
		public override void Accept(NodeVisitor visitor)
		{
			ISimpleNodeIterator children;
			INode child;
			
			if (visitor.ShouldRecurseSelf())
				visitor.VisitTag(this);
			if (visitor.ShouldRecurseChildren())
			{
				if (null != Children)
				{
					children = GetChildren();
					while (children.HasMoreNodes())
					{
						child = children.NextNode();
						child.Accept(visitor);
					}
				}
				if ((null != GetEndTag()) && (this != GetEndTag()))
					// 2nd guard handles <tag/>
					GetEndTag().Accept(visitor);
			}
		}
		
		/// <summary> Get the end tag for this tag.
		/// For example, if the node is {@.html <LABEL>The label</LABLE>}, then
		/// this method would return the {@.html </LABLE>} end tag.
		/// </summary>
		/// <returns> The end tag for this node.
		/// <em>Note: If the start and end position of the end tag is the same,
		/// then the end tag was injected (it's a virtual end tag).</em>
		/// </returns>
		public override ITag GetEndTag()
		{
			return (mEndTag);
		}
		
		/// <summary> Set the end tag for this tag.</summary>
		/// <param name="tag">The new end tag for this tag.
		/// Note: no checking is perfromed so you can generate bad HTML by setting
		/// the end tag with a name not equal to the name of the start tag,
		/// i.e. {@.html <LABEL>The label</TITLE>}
		/// </param>
		public override void SetEndTag(ITag tag)
		{
			mEndTag = tag;
		}
		
		/// <summary> Finds a text node, however embedded it might be, and returns
		/// it. The text node will retain links to its parents, so
		/// further navigation is possible.
		/// </summary>
		/// <param name="searchText">The text to search for.
		/// </param>
		/// <returns> The list of text nodes (recursively) found.
		/// </returns>
		public virtual IText[] DigupStringNode(System.String searchText)
		{
			NodeList nodeList = SearchFor(searchText);
			NodeList stringNodes = new NodeList();
			for (int i = 0; i < nodeList.Size(); i++)
			{
				INode node = nodeList.ElementAt(i);
				if (node is IText)
				{
					stringNodes.Add(node);
				}
				else
				{
					if (node is CompositeTag)
					{
						CompositeTag ctag = (CompositeTag) node;
						IText[] nodes = ctag.DigupStringNode(searchText);
						for (int j = 0; j < nodes.Length; j++)
							stringNodes.Add(nodes[j]);
					}
				}
			}
			IText[] stringNode = new IText[stringNodes.Size()];
			for (int i = 0; i < stringNode.Length; i++)
			{
				stringNode[i] = (IText) stringNodes.ElementAt(i);
			}
			return stringNode;
		}
		
		/// <summary> Return a string representation of the contents of this tag, it's children and it's end tag suitable for debugging.</summary>
		/// <returns> A textual representation of the tag.
		/// </returns>
		public override System.String ToString()
		{
			System.Text.StringBuilder ret;
			
			ret = new System.Text.StringBuilder(1024);
			ToString(0, ret);
			
			return (ret.ToString());
		}
		
		/// <summary> Return the text contained in this tag.</summary>
		/// <returns> The complete contents of the tag (within the angle brackets).
		/// </returns>
		public override System.String GetText()
		{
			System.String ret;
			
			ret = base.ToHtml();
			ret = ret.Substring(1, (ret.Length - 1) - (1));
			
			return (ret);
		}
		
		/// <summary> Return a string representation of the contents of this tag, it's children and it's end tag suitable for debugging.</summary>
		/// <param name="level">The indentation level to use.
		/// </param>
		/// <param name="buffer">The buffer to append to.
		/// </param>
		public virtual void  ToString(int level, System.Text.StringBuilder buffer)
		{
			INode node;
			
			for (int i = 0; i < level; i++)
				buffer.Append("  ");
			buffer.Append(base.ToString());
			buffer.Append(System.Environment.NewLine);
			for (ISimpleNodeIterator e = GetChildren(); e.HasMoreNodes(); )
			{
				node = e.NextNode();
				if (node is CompositeTag)
					((CompositeTag) node).ToString(level + 1, buffer);
				else
				{
					for (int i = 0; i <= level; i++)
						buffer.Append("  ");
					buffer.Append(node);
					buffer.Append(System.Environment.NewLine);
				}
			}
			
			if ((null != GetEndTag()) && (this != GetEndTag()))
				// 2nd guard handles <tag/>
				// eliminate virtual tags
				//            if (!(getEndTag ().getStartPosition () == getEndTag ().getEndPosition ()))
			{
				for (int i = 0; i <= level; i++)
					buffer.Append("  ");
				buffer.Append(GetEndTag().ToString());
				buffer.Append(System.Environment.NewLine);
			}
		}
	}
}
